﻿using System.Collections.Generic;
using System.Text;

namespace HtmlParser
{
    public class Scanner
    {
        #region Members

        private int m_currentIndex = 0;
        private string m_source = string.Empty;
        private List<HtmlTag> m_htmlList = new List<HtmlTag>();
        private int m_curentTag = 0;
        #endregion
       
        #region Public Methods
        /// <summary>
        /// Gets the next tag.
        /// </summary>
        /// <returns></returns>
        public HtmlTag GetNextTag()
        {
            if (HasNextTag())
            {
                return m_htmlList[m_curentTag++];
            }
            return null;
        }

        /// <summary>
        /// Sees the next tag.
        /// </summary>
        /// <param name="index">The index.</param>
        /// <returns></returns>
        public HtmlTag SeeNextTag(int index)
        {
            if (m_curentTag + index < m_htmlList.Count)
            {
                return m_htmlList[m_curentTag + index];
            }
            return null;
        }

        /// <summary>
        /// Determines whether [has next tag].
        /// </summary>
        /// <returns>
        /// 	<c>true</c> if [has next tag]; otherwise, <c>false</c>.
        /// </returns>
        public bool HasNextTag()
        {
            return m_curentTag < m_htmlList.Count;
        }

        /// <summary>
        /// Use to Parse a source Page into a List of Stoken
        /// </summary>
        /// <param name="source">The source Page</param>
        public void HtmlParse(string source)
        {
            Clear();
            m_source = source;
            Parse();
        }

        /// <summary>
        /// Clears this instance.
        /// </summary>
        public void Clear()
        {
            m_currentIndex = 0;
            m_source = string.Empty;
            if(m_htmlList!=null)
            {
                m_htmlList.Clear();
            }
            m_curentTag = 0;
        }
        #endregion

        #region Private Methods

        /// <summary>
        /// Parses this instance.
        /// </summary>
        private void Parse()
        {
            while (!EOf())
            {
                EatWhiteSpace();
                if (GetCurrentChar() == '<')
                {
                    Increase(1);
                    HtmlTag tag = ParseTag();
                    if (tag != null)
                    {
                        m_htmlList.Add(tag);
                    }
                }
                else
                {
                    m_htmlList.Add(PasseContent());
                }
            }
        }

        /// <summary>
        /// Determines whether [is white space] [the specified ch].
        /// </summary>
        /// <param name="ch">The ch.</param>
        /// <returns>
        /// 	<c>true</c> if [is white space] [the specified ch]; otherwise, <c>false</c>.
        /// </returns>
        private static bool IsWhiteSpace(char ch)
        {
            return ("\t\n\r ".IndexOf(ch) != -1);
        }

        /// <summary>
        /// Eats the white space.
        /// </summary>
        private void EatWhiteSpace()
        {
            while (!EOf())
            {
                if (!IsWhiteSpace(GetCurrentChar()))
                {
                    return;
                }
                m_currentIndex++;
            }
        }

        /// <summary>
        /// Gets the current char.
        /// </summary>
        /// <returns></returns>
        private char GetCurrentChar()
        {
            return GetCurrentChar(0);
        }

        /// <summary>
        /// Gets the current char.
        /// </summary>
        /// <param name="peek">The peek.</param>
        /// <returns></returns>
        private char GetCurrentChar(int peek)
        {
            if (m_currentIndex + peek < m_source.Length)
            {
                return m_source[m_currentIndex + peek];
            }
            return (char)0;
        }

        /// <summary>
        /// Es the of.
        /// </summary>
        /// <returns></returns>
        private bool EOf()
        {
            return m_currentIndex >= m_source.Length;
        }

        /// <summary>
        /// Increases the specified step.
        /// </summary>
        /// <param name="step">The step.</param>
        private void Increase(int step)
        {
            m_currentIndex += step;
        }

        /// <summary>
        /// Parses the tag.
        /// </summary>
        /// <param name="source">The source.</param>
        /// <returns></returns>
        private HtmlTag ParseTag()
        {
            #region Check If CommentTag
            StringBuilder tagName = new StringBuilder();
            if ((GetCurrentChar() == '!') &&
                (GetCurrentChar(1) == '-') &&
                (GetCurrentChar(2) == '-'))
            {
                tagName.Append("--");
                while (!EOf())
                {
                    if ((GetCurrentChar() == '-') &&
                        (GetCurrentChar(1) == '-') &&
                        (GetCurrentChar(2) == '>'))
                    {
                        Increase(3);
                        break;
                    }
                    Increase(1);
                }
                return null;
            }
            #endregion


            TagType isEndTag = TagType.Open;
            List<Attribute> attributes = new List<Attribute>();
            EatWhiteSpace();

            if (GetCurrentChar() == '/')
            {
                Increase(1); // Pass / Charater
                isEndTag = TagType.Close;
            }
            if (GetCurrentChar() == '!')
            {
                Increase(1);
            }
            #region GetTagName
            // If not the comment
            //Get the tagname

            while (!EOf())
            {
                if (GetCurrentChar() == '/' && GetCurrentChar(1) == '>')
                {
                    isEndTag = TagType.OpenClose;
                    Increase(1);
                    break;
                }
                if (IsWhiteSpace(GetCurrentChar()) ||
                    GetCurrentChar() == '>')
                {
                    break;
                }
                tagName.Append(GetCurrentChar());
                Increase(1);

            }

            EatWhiteSpace();
            #endregion
            //Find All Attributes
            #region Find All Attributes
            while (!EOf() && (GetCurrentChar() != '>'))
            {
                EatWhiteSpace();
                if (GetCurrentChar() == '/' && GetCurrentChar(1) == '>')
                {
                    isEndTag = TagType.OpenClose;
                    Increase(1);
                    break;
                }
                StringBuilder attributeName = new StringBuilder();
                char delim = (char)0;
                StringBuilder attributeValue = new StringBuilder();

                #region ParseAtributeName

                while (!EOf())
                {
                    if (IsWhiteSpace(GetCurrentChar()) ||
                        GetCurrentChar() == '=' ||
                        GetCurrentChar() == '>')
                    {
                        break;
                    }
                    if (GetCurrentChar() == '/' && GetCurrentChar(1) == '>')
                    {
                        isEndTag = TagType.OpenClose;
                        Increase(1);
                        break;
                    }
                    attributeName.Append(GetCurrentChar());
                    Increase(1);
                }
                EatWhiteSpace();

                if (GetCurrentChar() == '>')
                {
                    attributes.Add(new Attribute(attributeName.ToString(), string.Empty, delim));
                    break;
                }
                #endregion

                #region Parse Atribute Value
                if (GetCurrentChar() == '=')
                {
                    Increase(1); //Pass = Character
                    EatWhiteSpace();

                    if (GetCurrentChar() == '\'' || GetCurrentChar() == '\"')
                    {
                        delim = GetCurrentChar();
                        Increase(1); // Pass Delim Charater
                        while (GetCurrentChar() != delim)
                        {
                            attributeValue.Append(GetCurrentChar());
                            Increase(1);
                        }
                        Increase(1); //Pass Delim Character
                    }
                    else // Dont have Delim Character
                    {
                        while (!EOf() && !IsWhiteSpace(GetCurrentChar()) && GetCurrentChar() != '>')
                        {
                            if (GetCurrentChar() == '/' && GetCurrentChar(1) == '>')
                            {
                                isEndTag = TagType.OpenClose;
                                Increase(1);
                                break;
                            }
                            attributeValue.Append(GetCurrentChar());
                            Increase(1);
                        }
                    }
                }
                #endregion

                attributes.Add(new Attribute(attributeName.ToString(),
                                                    attributeValue.ToString(), delim));
            }
            #endregion

            Increase(1); // Pass > Character
            return TagFactory.CreateHtmlTag(tagName.ToString(), attributes, isEndTag);
        }

        /// <summary>
        /// Passes the content.
        /// </summary>
        /// <returns></returns>
        private HtmlTag PasseContent()
        {
            StringBuilder content = new StringBuilder();
            while (!EOf())
            {
                if (GetCurrentChar() == '<')
                {
                    break;
                }
                content.Append(GetCurrentChar());
                Increase(1);
            }

            return new HtmlContent(content.ToString());
        }
        #endregion
    }
}
